package com.wego.utils;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

/**
 * MD5工具类 - 提供安全的密码加密和验证功能
 */
public final class Md5Util {
    // 使用不可变的字符数组代替字符串数组，提高性能
    private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
    // 定义线程本地的MessageDigest实例，避免多线程创建的开销
    private static final ThreadLocal<MessageDigest> MD5_THREAD_LOCAL = ThreadLocal.withInitial(() -> {
        try {
            return MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 algorithm not found", e);
        }
    });

    private Md5Util() {
        // 禁止实例化工具类
        throw new AssertionError("工具类不允许实例化");
    }

    /**
     * 转换字节数组为16进制字串
     *
     * @param bytes 字节数组
     * @return 16进制字串
     */
    public static String bytesToHex(byte[] bytes) {
        // 使用StringBuilder代替StringBuffer，单线程环境下性能更好
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) {
            // 优化位运算，提高性能
            sb.append(HEX_DIGITS[(b >> 4) & 0x0f]);
            sb.append(HEX_DIGITS[b & 0x0f]);
        }
        return sb.toString();
    }

    /**
     * 使用盐值加密字符串
     *
     * @param origin 原始字符串
     * @param salt 盐值
     * @return 加密后的MD5字符串
     */
    public static String encode(String origin, String salt) {
        if (origin == null) {
            origin = "";
        }
        if (salt == null) {
            salt = "";
        }
        // 合并原始字符串和盐值，使用Base64编码增强安全性
        String combined = origin + Base64.getEncoder().encodeToString(salt.getBytes(StandardCharsets.UTF_8));
        return encode(combined);
    }

    /**
     * 加密字符串
     *
     * @param origin 原始字符串
     * @return 加密后的MD5字符串
     */
    public static String encode(String origin) {
        if (origin == null) {
            origin = "";
        }

        try {
            // 获取线程本地的MessageDigest实例
            MessageDigest md = MD5_THREAD_LOCAL.get();
            // 重置MessageDigest，避免上一次计算的残留
            md.reset();
            // 计算MD5摘要
            byte[] digest = md.digest(origin.getBytes(StandardCharsets.UTF_8));
            // 转换为十六进制字符串
            return bytesToHex(digest);
        } catch (Exception e) {
            // 记录异常信息，但不打印堆栈（避免敏感信息泄露）
            throw new RuntimeException("MD5 encryption failed", e);
        }
    }

    /**
     * 验证原始值和盐值加密后是否等于目标MD5值
     *
     * @param origin 原始值
     * @param salt 盐值
     * @param md5 目标MD5值
     * @return 验证结果
     */
    public static boolean verify(String origin, String salt, String md5) {
        if (md5 == null) {
            return false;
        }
        // 避免时序攻击的安全比较
        return safeEquals(encode(origin, salt), md5);
    }

    /**
     * 验证原始值加密后是否等于目标MD5值
     *
     * @param origin 原始值
     * @param md5 目标MD5值
     * @return 验证结果
     */
    public static boolean verify(String origin, String md5) {
        if (md5 == null) {
            return false;
        }
        // 避免时序攻击的安全比较
        return safeEquals(encode(origin), md5);
    }

    /**
     * 安全的字符串比较方法，防止时序攻击
     *
     * @param a 第一个字符串
     * @param b 第二个字符串
     * @return 比较结果
     */
    private static boolean safeEquals(String a, String b) {
        if (a == null || b == null || a.length() != b.length()) {
            return false;
        }

        int result = 0;
        for (int i = 0; i < a.length(); i++) {
            result |= a.charAt(i) ^ b.charAt(i);
        }
        return result == 0;
    }

    /**
     * 清除ThreadLocal中的资源，避免内存泄漏
     */
    public static void clearThreadLocal() {
        MD5_THREAD_LOCAL.remove();
    }

    public static void main(String[] args) {
        final String origin = "123456";
        System.out.println(encode(origin));

        final String salt = "iTtuSKZxwRAaW4kilHfZ5fQl0Q1q";
        final String md5 = encode(origin, salt);
        System.out.println(md5);
        final boolean res = verify(origin, salt, md5);
        System.out.println(res);
    }
}

